/********************************************************************************/
/*										*/
/*		ClideDatabaseManager.java					*/
/*										*/
/*	Class for accessing a CLIME database through clide			*/
/*										*/
/********************************************************************************/
/*	Copyright 1998 Brown University -- Steven P. Reiss		      */
/*********************************************************************************
 *  Copyright 1998, Brown University, Providence, RI.				 *
 *										 *
 *			  All Rights Reserved					 *
 *										 *
 *  Permission to use, copy, modify, and distribute this software and its	 *
 *  documentation for any purpose other than its incorporation into a		 *
 *  commercial product is hereby granted without fee, provided that the 	 *
 *  above copyright notice appear in all copies and that both that		 *
 *  copyright notice and this permission notice appear in supporting		 *
 *  documentation, and that the name of Brown University not be used in 	 *
 *  advertising or publicity pertaining to distribution of the software 	 *
 *  without specific, written prior permission. 				 *
 *										 *
 *  BROWN UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS		 *
 *  SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND		 *
 *  FITNESS FOR ANY PARTICULAR PURPOSE.  IN NO EVENT SHALL BROWN UNIVERSITY	 *
 *  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 	 *
 *  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,		 *
 *  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS		 *
 *  ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 	 *
 *  OF THIS SOFTWARE.								 *
 *										 *
 ********************************************************************************/

/* RCS: $Header: /pro/spr_cvs/pro/clime/javasrc/edu/brown/clime/clide/ClideDatabaseManager.java,v 1.14 2003-04-11 21:04:28 spr Exp $ */


/*********************************************************************************
 *
 * $Log: ClideDatabaseManager.java,v $
 * Revision 1.14  2003-04-11 21:04:28  spr
 * Better error output on database failures
 *
 * Revision 1.13  2003/04/10 21:13:31  spr
 * Change to handle dates as longs in sql.
 *
 * Revision 1.12  2003/04/09 21:12:31  spr
 * Use both possible postgres names at load time.
 *
 * Revision 1.11  2003/04/07 21:02:57  spr
 * Get the database name from the environment/properties.
 *
 * Revision 1.10  2003/04/04 23:37:37  spr
 * Add code to display enums as text; add private fields for default display.
 *
 * Revision 1.9  2003/04/03 19:44:21  spr
 * Keep track of number of database opens/closes; add comparison to Location.
 *
 * Revision 1.8  2003/04/01 18:22:59  spr
 * Update code to use IVY rather than BLOOM utilities.
 *
 * Revision 1.7  2002/12/14 01:14:14  spr
 * Modify the method to get a database handle to include a flag indicating whether to
 * open the database or not.
 *
 * Revision 1.6  2002/12/12 14:54:08  spr
 * Update call to open database so that database not opened for delete/setup.
 *
 * Revision 1.5  2002/09/25 12:21:11  spr
 * Modifications to use less memory on initial load.
 *
 * Revision 1.4  2002/07/22 14:49:23  cmkenned
 * *** empty log message ***
 *
 * Revision 1.2  2002/06/20 16:34:33  spr
 * Formatting changes.
 *
 * Revision 1.1.1.1  2002/05/31 16:59:35  spr
 * Initial version
 *
 *
 ********************************************************************************/


package edu.brown.clime.clide;


import edu.brown.clime.clip.*;

import edu.brown.cs.ivy.xml.*;

import org.xml.sax.*;
import org.w3c.dom.*;

import java.util.*;
import java.io.*;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.DriverManager;



public class ClideDatabaseManager implements ClideDatabase, ClideConstants
{


/********************************************************************************/
/*										*/
/*	Private Storage 							*/
/*										*/
/********************************************************************************/


private String db_project;
private String db_name;
private String db_owner;
private String db_user;
private String db_group;
private String db_password;
private int open_count;

private Connection sql_conn;

private long next_id;
private long id_count;
private long id_request;

private Statement cur_batch;


private ClideDataModel	data_model = ClideModelManager.getDataModel();




/********************************************************************************/
/*										*/
/*	Static Initializers							*/
/*										*/
/********************************************************************************/

static {
   try {
      Class.forName(CLIDE_DATABASE_CLASS);
    }
   catch (ClassNotFoundException ex) {
      try {
	 Class.forName(CLIDE_DATABASE_CLASS1);
       }
      catch (ClassNotFoundException ex1) {
	 System.err.println("CLIDE: Can't find database class");
	 System.exit(1);
       }
    }
}




/********************************************************************************/
/*										*/
/*	Static factory methods							*/
/*										*/
/********************************************************************************/

private static HashMap active_databases = new HashMap();



static public ClideDatabase findDatabase(String proj,String owner,String group,boolean open)
   throws ClideException
{
   String user = System.getProperty("user.name");

   String key = proj + "@" + owner + "@" + group + "@" + user;
   ClideDatabaseManager db = (ClideDatabaseManager) active_databases.get(key);
   if (db == null) {
      db = new ClideDatabaseManager(proj,user,owner,group,open);
      active_databases.put(key,db);
    }
   else if (open) db.openDatabase();

   return db;
}



static public ClideDatabase findDatabase(ClipProject cp,boolean open)
   throws ClideException
{
   return findDatabase(cp.getName(),cp.getOwner(),cp.getGroup(),open);
}



/********************************************************************************/
/*										*/
/*	Constructors								*/
/*										*/
/********************************************************************************/

private ClideDatabaseManager(String proj,String user,String owner,String group,boolean openit)
{
   db_name = "clide_" + proj + "_" + owner;
   db_project = proj;
   db_owner = owner;
   db_user = user;
   db_group = group;
   db_password = CLIDE_DATABASE_PASSWORD;
   if (db_password == null) db_password = "";
   next_id = 0;
   id_count = 0;
   id_request = 128;
   cur_batch = null;
   sql_conn = null;
   open_count = 0;

   if (openit) {
      try {
	 openDatabase();
       }
      catch (ClideException e) {
	 sql_conn = null;
       }
    }
}



/********************************************************************************/
/*										*/
/*	Methods to open and close a database					*/
/*										*/
/********************************************************************************/

private synchronized void openDatabase() throws ClideException
{
   if (sql_conn == null) {
      try {
	 sql_conn = DriverManager.getConnection(CLIDE_DATABASE_PREFIX + db_name,db_user,db_password);
	 if (!checkVersion()) closeDatabase();
	 evalCommand("SET TIME ZONE LOCAL");
	 open_count = 1;
       }
      catch (SQLException e) {
	 throw new ClideException("Can't open database " + db_name + ": " + e.getMessage());
       }
    }
   else ++open_count;
}




public synchronized void closeDatabase()
{
   --open_count;
   if (open_count > 0) return;

   if (sql_conn != null) {
      try {
	 sql_conn.close();
       }
      catch (SQLException e) { }
    }

   sql_conn = null;
}



public boolean exists()
{
   return (sql_conn != null);
}



private boolean checkVersion() throws ClideException, SQLException
{
   if (sql_conn == null) return false;

   String mdver = data_model.getModelVersion();
   if (mdver == null) return true;

   String dbver = null;
   ResultSet rs = queryDatabase("SELECT Version FROM ClideVersion");
   if (rs.next()) {
      dbver = rs.getString(1);
    }
   rs.close();
   if (dbver == null) return false;
   if (!dbver.equalsIgnoreCase(mdver)) return false;

   return true;
}




/********************************************************************************/
/*										*/
/*	Methods to remove a database						*/
/*										*/
/********************************************************************************/

public synchronized void removeDatabase()
{
   boolean fg = (sql_conn != null);

   closeDatabase();

   try {
      Connection mconn = DriverManager.getConnection(
	 CLIDE_DATABASE_PREFIX + CLIDE_MASTER_DATABASE,db_user,db_password);
      Statement st = mconn.createStatement();
      st.executeUpdate("DROP DATABASE " + db_name);
      mconn.close();
    }
   catch (SQLException e) {
      if (fg) {
	 System.err.println("CLIDE: Problem removing database " + db_name + ": " + e.getMessage());
       }
    }
}



/********************************************************************************/
/*										*/
/*	Methods to create a database						*/
/*										*/
/********************************************************************************/


public synchronized void createDatabase() throws ClideException
{
   try {
      Connection mconn = DriverManager.getConnection(
	 CLIDE_DATABASE_PREFIX + CLIDE_MASTER_DATABASE,db_user,db_password);
      Statement st = mconn.createStatement();
      st.executeUpdate("CREATE DATABASE " + db_name);
      mconn.close();
    }
   catch (SQLException e) {
      System.err.println("CLIDE: Problem creating database " + db_name +
			    " for user " + db_user + " with password `" + db_password + "' " +
			    ": " + e.getMessage());
      System.err.println("CLIDE: Master: " + CLIDE_DATABASE_PREFIX + CLIDE_MASTER_DATABASE);
      e.printStackTrace();
    }

   try {
      sql_conn = DriverManager.getConnection(CLIDE_DATABASE_PREFIX + db_name,db_user,db_password);
    }
   catch (SQLException e) {
      System.err.println("CLIDE: Problem accessing new database " + db_name + ": " + e.getMessage());
      System.exit(1);
    }

   for (Iterator ti = data_model.getTables(); ti.hasNext(); ) {
      ClideDataModel.Table t = (ClideDataModel.Table) ti.next();
      createTable(t);
    }

   for (Iterator fi = data_model.getFunctions(); fi.hasNext(); ) {
      ClideDataModel.Function f = (ClideDataModel.Function) fi.next();
      createFunction(f);
    }

   for (Iterator vi = data_model.getViews(); vi.hasNext(); ) {
      ClideDataModel.View v = (ClideDataModel.View) vi.next();
      createView(v);
    }

   ClideModelManager mmgr = (ClideModelManager) data_model;
   mmgr.loadSystemValues(this);

   try {
      Date curts = new Date(0);
      Statement stmt = sql_conn.createStatement();
      String vupd = "INSERT INTO ClideVersion(Version,UserDate) VALUES ( ";
      vupd += "'" + data_model.getModelVersion() + "', ";
      vupd += "'" + curts.getTime() + "')";
      stmt.executeUpdate(vupd);
      stmt.close();
    }
   catch (SQLException e) {
      System.err.println("CLIDE: Problem setting version for " + db_name + ": " + e.getMessage());
    }
}




private void createTable(ClideDataModel.Table t) throws ClideException
{
   StringBuffer buf = new StringBuffer();
   Statement st = null;

   buf.append("CREATE TABLE ");
   buf.append(t.getName());
   buf.append(" (");

   int fct = 0;
   for (Iterator fi = t.getFields(); fi.hasNext(); ) {
      ClideDataModel.Field f = (ClideDataModel.Field) fi.next();
      ClideDataModel.Type ft = f.getType();
      if (fct++ != 0) buf.append(",");
      buf.append(" ");
      buf.append(f.getName());
      buf.append(" ");
      buf.append(ft.getSqlName());
      buf.append(" ");
      buf.append("DEFAULT ");
      buf.append(ft.getDefaultValue());
      if (!f.getIsOptional()) buf.append(" NOT NULL");
      else buf.append(" NULL");
    }

   ClideDataModel.Field [] kf = t.getKey();
   if (kf != null && kf.length > 0) {
      if (fct++ != 0) buf.append(",");
      buf.append(" PRIMARY KEY (");
      int kct = 0;
      for (int i = 0; i < kf.length; ++i) {
	 if (kct++ != 0) buf.append(",");
	 buf.append(" ");
	 buf.append(kf[i].getName());
       }
      buf.append(")");
    }
   buf.append(" )");

   try {
      st = sql_conn.createStatement();
      st.executeUpdate(buf.toString());
    }
   catch (SQLException e) {
      System.err.println("CLIDE: Problem creating table: " + e.getMessage());
      System.err.println("CLIDE: Command was " + buf.toString());
      throw new ClideException(e.getMessage());
    }

   int idxctr = 0;
   for (Iterator ii = t.getIndices(); ii.hasNext(); ) {
      ClideDataModel.Index idx = (ClideDataModel.Index) ii.next();
      ClideDataModel.Field [] ifld = idx.getFields();
      buf = new StringBuffer();
      buf.append("CREATE INDEX ");
      buf.append(t.getName() + "_INDEX_" + idxctr++);
      buf.append(" ON ");
      buf.append(t.getName());
      buf.append(" (");
      for (int i = 0; i < ifld.length; ++i) {
	 buf.append(" ");
	 buf.append(ifld[i].getName());
       }
      buf.append(" )");
      try {
	 st.executeUpdate(buf.toString());
       }
      catch (SQLException e) {
	 System.err.println("CLIDE: Problem creating index: " + e.getMessage());
	 System.err.println("CLIDE: Command was " + buf.toString());
	 throw new ClideException(e.getMessage());
       }
    }
}




private void createFunction(ClideDataModel.Function f) throws ClideException
{
   StringBuffer buf = new StringBuffer();

   buf.append("CREATE FUNCTION ");
   buf.append(f.getName() + "(" + f.getArgumentTypeList() + ")");
   buf.append(" RETURNS " + f.getReturnType());
   buf.append(" AS " + f.getDefinition());
   buf.append(" LANGUAGE '" + f.getLanguage() + "'");

   String att = f.getAttributes();
   if (att != null) {
      buf.append(" WITH (" + att + ")");
    }

   try {
      Statement st = sql_conn.createStatement();
      st.executeUpdate(buf.toString());
    }
   catch (SQLException e) {
      System.err.println("CLIDE: Problem creating function: " + e.getMessage());
      System.err.println("CLIDE: Command was " + buf.toString());
      throw new ClideException(e.getMessage());
    }
}




private void createView(ClideDataModel.View v) throws ClideException
{
   StringBuffer buf = new StringBuffer();

   buf.append("CREATE VIEW " + v.getName());
   buf.append(" AS SELECT ");

   int ct = 0;
   for (Iterator fi = v.getFields(); fi.hasNext(); ) {
      ClideDataModel.Field f = (ClideDataModel.Field) fi.next();
      if (ct++ != 0) buf.append(", ");
      buf.append(f.getSource() + " AS " + f.getName());
    }

   buf.append(" FROM ");
   ct = 0;
   for (Iterator ti = v.getTables();  ti.hasNext(); ) {
      ClideDataModel.Table t = (ClideDataModel.Table) ti.next();
      String tnm = v.getTableName(t);
      if (ct++ != 0) buf.append(", ");
      buf.append(t.getName());
      if (!t.getName().equals(tnm)) buf.append(" " + tnm);
    }

   buf.append(" WHERE " + v.getCondition());

   try {
      Statement st = sql_conn.createStatement();
      st.executeUpdate(buf.toString());
    }
   catch (SQLException e) {
      System.err.println("CLIDE: Problem creating view " + v.getName() + ": " + e.getMessage());
      System.err.println("CLIDE: Command was " + buf.toString());
      throw new ClideException(e.getMessage());
    }
}




/********************************************************************************/
/*										*/
/*	Update commands 							*/
/*										*/
/********************************************************************************/

public synchronized void updateDatabase(String outf,String [] updfiles,boolean newdb)
   throws ClideException
{
   if (updfiles == null) return;

   Vector tbls = new Vector();
   HashMap tblmap = new HashMap();
   HashMap idtable = new HashMap();

   IvyXmlWriter xw = null;

   try {
      xw = new IvyXmlWriter(new FileWriter(outf));
    }
   catch (IOException e) {
      System.err.println("CLIDE: Can't open output file " + outf);
      return;
    }

   xw.begin("CLIME:CHANGES");
   xw.field("PROJECT",db_project);
   xw.field("OWNER",db_owner);
   xw.field("USER",db_user);
   xw.field("GROUP",db_group);

   try {
      updateUserData(idtable,xw);
    }
   catch (ClideException e) {
      System.err.println("CLIDE: Problem updating user data: " + e.getMessage());
    }

   for (int j = 0; j < updfiles.length; ++j) {
      ClideTableUpdater.parseXml(this,updfiles[j],idtable,xw,newdb,tbls);
    }

   for (Iterator it = tbls.iterator(); it.hasNext(); ) {
      ClideTableUpdater tu = (ClideTableUpdater) it.next();
      tu.preprocess();
      tblmap.put(tu.getTableName(),tu);
    }

   for (Iterator it = data_model.getTablesInMatchOrder(); it.hasNext(); ) {
      ClideDataModel.Table t = (ClideDataModel.Table) it.next();
      ClideTableUpdater tu = (ClideTableUpdater) tblmap.get(t.getName());
      if (tu != null) tu.processIds();
    }

   for (Enumeration e = tbls.elements(); e.hasMoreElements(); ) {
      ClideTableUpdater tu = (ClideTableUpdater) e.nextElement();
      tu.process();
    }

   for (Iterator it = data_model.getTables(); it.hasNext(); ) {
      ClideDataModel.Table t = (ClideDataModel.Table) it.next();
      ClideDataModel.Computation tc = t.getComputation();
      if (tc != null) {
	 boolean chng = false;
	 for (Iterator it1 = tc.getDependsOnTableNames(); !chng && it1.hasNext(); ) {
	    String dtn = (String) it1.next();
	    if (tblmap.get(dtn) != null) chng = true;
	  }
	 if (chng) {
	    tc.setupRelation(this,xw);
	  }
       }
    }

   xw.end();

   xw.close();
}




private void updateUserData(HashMap idtable,IvyXmlWriter xw) throws ClideException
{
   if (sql_conn == null) return;

   Date curd = new Date();

   long lmod = 0;
   try {
      ResultSet rs = queryDatabase("SELECT UserDate FROM ClideVersion");
      if (rs.next()) {
	 lmod = rs.getLong(1);
       }
      rs.close();
    }
   catch (SQLException e) {
      throw new ClideException("SQL error: " + e.getMessage());
    }

   ClideModelManager mmgr = (ClideModelManager) data_model;
   if (mmgr.loadUserValues(this,lmod,idtable,xw)) {
      String upd = "UPDATE ClideVersion SET UserDate = '" + curd.getTime() + "'";
      batchCommand(upd);
      endBatch();
    }
}



/********************************************************************************/
/*										*/
/*	Access commands 							*/
/*										*/
/********************************************************************************/

public synchronized ResultSet queryDatabase(String q) throws ClideException
{
   if (sql_conn == null) return null;

   ResultSet rs = null;

   try {
      Statement stmt = sql_conn.createStatement();
      rs = stmt.executeQuery(q);
    }
   catch (SQLException e) {
      throw new ClideException("SQL problem: " + e.getMessage());
    }

   return rs;
}



public synchronized void evalCommand(String q) throws ClideException
{
   if (sql_conn == null) return;

   try {
      Statement stmt = sql_conn.createStatement();
      stmt.executeUpdate(q);
    }
   catch (SQLException e) {
      throw new ClideException("SQL problem: " + e.getMessage());
    }
}



public synchronized void batchCommand(String q) throws ClideException
{
   if (sql_conn == null) return;

   try {
      if (cur_batch == null) {
	 cur_batch = sql_conn.createStatement();
	 cur_batch.clearBatch();
       }
      cur_batch.addBatch(q);
    }
   catch (SQLException e) {
      throw new ClideException("SQL problem: " + e.getMessage());
    }
}



public synchronized void endBatch() throws ClideException
{
   if (sql_conn == null || cur_batch == null) return;

   try {
      cur_batch.executeBatch();
      cur_batch.close();
      cur_batch = null;
    }
   catch (SQLException e) {
      throw new ClideException("SQL problem: " + e.getMessage());
    }
}




/********************************************************************************/
/*										*/
/*	Description entries							*/
/*										*/
/********************************************************************************/


public synchronized Location getLocationForId(ClideDataModel.TableView tbl,String id)
{
   ClideDataModel.Location dml = tbl.getLocation();
   if (dml == null) return null;

   return buildLocation(tbl,dml,id);
}




public synchronized String getDescriptionForId(ClideDataModel.TableView tbl,String id)
{
   if (tbl == null) return null;

   return formatItem(tbl,tbl.getDescription(),id);
}



private LocBase buildLocation(ClideDataModel.TableView tbl,ClideDataModel.Location dml,String id)
{
   StringBuffer q = new StringBuffer();
   q.append("SELECT ");
   int ctr = 0;
   if (dml.getFileField() != null) {
      if (ctr++ != 0) q.append(", ");
      q.append("X." + dml.getFileField().getName());
    }
   if (dml.getStartField() != null) {
      if (ctr++ != 0) q.append(", ");
      q.append("X." + dml.getStartField().getName());
    }
   if (dml.getEndField() != null) {
      if (ctr++ != 0) q.append(", ");
      q.append("X." + dml.getEndField().getName());
    }
   if (ctr == 0) return null;
   q.append(" FROM " + tbl.getName() + " X");
   q.append(" WHERE X." + tbl.getIdField().getName() + " = '" + id + "'");

   String file = null;
   int start = 0;
   int end = 0;

   try {
      ResultSet rs = queryDatabase(q.toString());
      if (rs.next()) {
	 ctr = 1;
	 if (dml.getFileField() != null) file = rs.getString(ctr++);
	 if (dml.getStartField() != null) start = rs.getInt(ctr++);
	 if (dml.getEndField() != null) end = rs.getInt(ctr++);
       }
    }
   catch (ClideException e) {
      System.err.println("CLIDE: id problem: " + e.getMessage());
    }
   catch (SQLException e) {
      System.err.println("CLIDE: SQL id problem: " + e.getMessage());
    }

   if (file == null) return null;

   return new LocBase(file,start,end);
}




private String formatItem(ClideDataModel.TableView tbl,ClideDataModel.Description d,String id)
{
   if (d == null) return null;

   HashMap hm = new HashMap();
   StringBuffer q = new StringBuffer();
   String var = "X";

   q.append("SELECT ");
   int ctr = 0;
   for (Iterator it = d.getFields(); it.hasNext(); ) {
      ClideDataModel.Field f = (ClideDataModel.Field) it.next();
      if (hm.get(f.getName()) == null) {
	 if (ctr++ != 0) q.append(", ");
	 q.append(var + "." + f.getName());
	 hm.put(f.getName(),new Integer(ctr));
       }
    }
   q.append(" FROM " + tbl.getName() + " " + var);
   q.append(" WHERE " + var + "." + tbl.getIdField().getName() + " = '" + id + "'");

   String rslt = null;

   try {
      ResultSet rs = queryDatabase(q.toString());
      if (rs != null && rs.next()) {
	 for (Iterator it = hm.entrySet().iterator(); it.hasNext(); ) {
	    Map.Entry ent = (Map.Entry) it.next();
	    String fnm = (String) ent.getKey();
	    ClideDataModel.Field f = tbl.findField(fnm);
	    int idx = ((Integer) ent.getValue()).intValue();
	    String v = rs.getString(idx);
	    v = f.getType().displayValue(v);
	    ent.setValue(v);
	  }
	 rslt = generateString(d.getFormatString(),hm);
       }
    }
   catch (ClideException e) {
      System.err.println("CLIDE: id problem: " + e.getMessage());
    }
   catch (SQLException e) {
      System.err.println("CLIDE: SQL id problem: " + e.getMessage());
    }

   return rslt;
}



private String generateString(String fmt,HashMap hm)
{
   StringBuffer rslt = new StringBuffer();
   StringBuffer vnm = null;

   for (int i = 0; i < fmt.length(); ++i) {
      char c = fmt.charAt(i);
      if (vnm != null) {
	 if (c == ')') {
	    String var = vnm.toString();
	    String val = (String) hm.get(var);
	    if (val != null) rslt.append(val);
	    vnm = null;
	  }
	 else vnm.append(c);
       }
      else if (c == '$' && i+3 < fmt.length() && fmt.charAt(i+1) == '(') {
	 ++i;
	 vnm = new StringBuffer();
       }
      else rslt.append(c);
    }

   return rslt.toString();
}




private static class LocBase implements ClideDatabase.Location {

   private String file_name;
   private int start_line;
   private int end_line;

   LocBase(String f,int s,int e) {
      file_name = f;
      start_line = s;
      end_line = e;
    }

   public String getFile()		{ return file_name; }
   public int getStartLine()		{ return start_line; }
   public int getEndLine()		{ return end_line; }

   public String toString() {
      if (file_name == null && start_line == 0) return "";
      StringBuffer buf = new StringBuffer();
      if (file_name != null) {
	 buf.append(file_name);
	 if (start_line > 0) buf.append(": ");
       }
      if (start_line > 0) buf.append(start_line);
      if (end_line > start_line) {
	 buf.append("-");
	 buf.append(end_line);
       }
      return buf.toString();
    }

   public int compareTo(Location l) {
      if (l == null) return 1;
      int v = file_name.compareTo(l.getFile());
      if (v != 0) return v;
      if (start_line < l.getStartLine()) return -1;
      else if (start_line > l.getStartLine()) return 1;
      if (end_line < l.getEndLine()) return -1;
      else if (end_line > l.getEndLine()) return 1;
      return 0;
    }

   public boolean equals(Object o) {
      if (o == null || !(o instanceof LocBase)) return false;
      LocBase l = (LocBase) o;
      return file_name.equals(l.file_name) && start_line == l.start_line &&
	 end_line == l.end_line;
    }

}	// end of subclass LocBase




/********************************************************************************/
/*										*/
/*	Methods for handling unique identifiers 				*/
/*										*/
/********************************************************************************/

public String getNextId()
{
   if (sql_conn == null) return "_0";

   if (id_count <= 0) {
      try {
	 Statement st = sql_conn.createStatement();
	 st.executeUpdate("BEGIN");
	 st.executeUpdate("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
	 ResultSet rs = st.executeQuery("SELECT NextId FROM ClideIdNumber");
	 if (rs.next()) next_id = rs.getLong(1);
	 else next_id = 0;
	 id_count = id_request;
	 String upd = "UPDATE ClideIdNumber SET NextId = ";
	 upd += (next_id + id_request);
	 if (id_request < 1024) id_request *= 2;
	 st.executeUpdate(upd);
	 st.executeUpdate("COMMIT");
       }
      catch (SQLException e) {
	 System.err.println("CLIDE: Problem getting more ids: " + e.getMessage());
       }
    }

   --id_count;
   return "_" + next_id++;
}



}	// end of class ClideDatabaseManager




/* end of ClideDatabaseManager.java */




























